﻿using Consul;
using M.Core.Utils;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;

namespace M.Core
{
    public class ConsulContext
    {
        public ConsulClient consulClient { get; set; }


    }
    public static class ConsulExtensions
    {
        public class ServiceOptions
        {
            public string IP { get; set; }
            public int Port { get; set; }
            public string ConsulIP { get; set; }
            public int ConsulPort { get; set; }
            public float Version { get; set; }
            public int ThriftPort { get; set; }
        }
        /// <summary>
        /// 注入Consul对象
        /// </summary>
        /// <param name="services"></param>
        /// <returns></returns>
        public static IServiceCollection AddConsul(this IServiceCollection services)
        {
            services.AddSingleton<ConsulContext>();
            services.AddSingleton<ServiceDiscovery>();
            return services;
        }


        /// <summary>
        /// 使用Consul对象并注册服务配置Thrift接口和服务版本
        /// </summary>
        /// <param name="builder"></param>
        /// <param name="serviceEntity"></param>
        public static void UseConsul(this IApplicationBuilder builder, ServiceOptions serviceEntity)
        {
            //var features = builder.Properties["server.Features"] as FeatureCollection;
            //var addessess = features.Get<IServerAddressesFeature>().Addresses.Select(url => new Uri(url));


            builder.Map("/api/health", i =>
            {
                i.Run(async u =>
                {
                    await u.Response.WriteAsync("OK");
                });
            });
            ConsulContext consulContext = builder.ApplicationServices.GetService<ConsulContext>();

            var consulClient = new ConsulClient();

            //注册服务到Consul
            string serviceID = string.Empty;
            serviceID = RegisterConsul(consulClient, serviceEntity);

            consulContext.consulClient = consulClient;

            var services = builder.ApplicationServices.CreateScope().ServiceProvider;

            var lifeTime = services.GetService<IApplicationLifetime>();

            lifeTime.ApplicationStarted.Register(() => {
                CheckConsul(consulContext, builder, serviceEntity);                
            });
            lifeTime.ApplicationStopping.Register(() =>
            {
                //从Consul取消注册服务
                consulContext.consulClient.Agent.ServiceDeregister(serviceID).GetAwaiter().GetResult();//服务停止时取消注册
            });

        }
        private static string RegisterConsul(ConsulClient consulClient, ServiceOptions serviceEntity,bool isIIS=false)
        {

            var ip = System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces()
                      .Select(p => p.GetIPProperties())
                      .SelectMany(p => p.UnicastAddresses)
                      .Where(p => p.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork && !System.Net.IPAddress.IsLoopback(p.Address))
                      .FirstOrDefault()?.Address.ToString();

            if (!string.IsNullOrWhiteSpace(ip))
            {
                serviceEntity.IP = ip;
            }

            consulClient.Config.Address = new Uri($"http://{serviceEntity.ConsulIP}:{serviceEntity.ConsulPort}");//请求注册的 Consul 地址

            var httpCheck = new AgentServiceCheck()
            {
                DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(2),//服务启动多久后注册
                Interval = TimeSpan.FromSeconds(2),//健康检查时间间隔，或者称为心跳间隔
                HTTP = $"http://{serviceEntity.IP}:{serviceEntity.Port}/api/health",//健康检查地址
                Timeout = TimeSpan.FromSeconds(3),
            };

            var ServiceName = Assembly.GetEntryAssembly().GetName().Name;
            var ServiceClientID = Md5.CreateMD5($"{serviceEntity.IP}{serviceEntity.Port}{ServiceName}{serviceEntity.Version}");

            //这里实现把服务信息注册到consul 
            var dic = new Dictionary<string, string>();
            dic.Add("Version", serviceEntity.Version.ToString());
            dic.Add("ThriftPort", serviceEntity.ThriftPort.ToString());

            // Register service with consul
            var registration = new AgentServiceRegistration()
            {
                Checks = new[] { httpCheck },
                ID = ServiceClientID,
                Name = Assembly.GetEntryAssembly().GetName().Name,
                Address = serviceEntity.IP,
                Port = serviceEntity.Port,
                Tags = new[] { $"urlprefix-/{Assembly.GetEntryAssembly().GetName().Name}" },//添加 urlprefix-/servicename 格式的 tag 标签，以便 Fabio 识别
                Meta = dic
                
            };

            consulClient.Agent.ServiceRegister(registration).Wait();//服务启动时注册，内部实现其实就是使用 Consul API 进行注册（HttpClient发起）

            return ServiceClientID;
        }
        public static void CheckConsul(ConsulContext consulContext, IApplicationBuilder applicationBuilder, ServiceOptions serviceEntity)
        {
            var logger = applicationBuilder.ApplicationServices.GetService<ILogger<ConsulContext>>();

            Task.Factory.StartNew(async () =>
            {
            Restart:
                var members = await consulContext.consulClient.Agent.Members(false);

                if (members.StatusCode == System.Net.HttpStatusCode.OK)
                {
                    var response = members.Response;
                    while (true)
                    {
                        await Task.Delay(1000);

                        try
                        {
                            var task = consulContext.consulClient.Health.State(HealthStatus.Any);
                            //task.RunSynchronously();
                            task.GetAwaiter().GetResult();

                        }
                        catch (Exception ex)
                        {
                            logger.LogError(ex, "检查consul发生错误!");

                            var address = consulContext.consulClient.Config.Address.ToString();

                            foreach (var item in response)
                            {
                                var addr = $"http://{item.Addr}:8500/";
                                if (address.Equals(addr)) continue;

                                serviceEntity.ConsulIP = item.Addr;
                                serviceEntity.ConsulPort = 8500;

                                var consulClient = new ConsulClient();
                                var id = RegisterConsul(consulClient, serviceEntity);
                                var old=consulContext.consulClient;
                                consulContext.consulClient = consulClient;
                                old.Dispose();

                                goto Restart;
                            }
                        }

                    }
                }
            });
        }
    }
}
